12장 함수
함수 리터럴
리터럴
: 사람이 이해할 수 있는 문자 또는 약속된 기호를 사용해 값을 생성하는 표기 방식. 즉, 값을 생성하기 위한 표기법
함수 리터럴도 평가되어 값을 생성함 → 생성된 값은 객체. 즉, 함수는 객체다
하지만 일반객체와 다르게 호출할 수 있고, 고유의 프로퍼티를 가진다.
함수 정의
크게 4가지 방법이 있음
- 함수 선언문
- 함수 표현식
- Function 생성자 함수
- 화살표 함수
🧠 선언과 정의 차이는 메모리에 할당이 되는가 라고 한다. JS 에서는 함수를 선언만할 수가 없다. 어쨋든 함수 객체가 생성되서 할당되니까. 그래서 이렇게 말하는 듯. 근데 사실 JS 에서는 변수도 선언만 할 수가 없다. undefined 값이 메모리에 할당 되니까. #뇌피셜
함수 선언문
함수 선언문과 리터럴 형태가 동일함. 함수 리터럴은 함수 이름 생략 가능, 함수 선언문은 생략 불가능
함수 선언문은 표현식이 아닌 문
이다. 따라서 콘솔에서 함수 선언문 실행하면 완료됨을 알리는 undefined가 출력된다. 표현식이였으면 생성된 함수가 출력되었을 것.
표현식이 아닌 문은 변수에 할당할 수가 없음.
var add = function add(x, y) {
return x + y;
};
띠용? 어케했냐. 함수 리터럴과 선언문은 형태가 동일함. 따라서 JS 엔진에서 알아서 함수 리터럴로 해석한 것. JS엔진은 문맥에 따라서 중의적인 코드를 알아서 해석
함수 내부 동작에 차이가 있음.
function foo() { console.log('foo'); }
foo(); // foo
(function bar() { console.log('bar'); });
bar();
그룹 연산자 ( ) 안에는 값으로 평가되는 표현식만 올 수 있음 → bar가 선언문이 아닌 함수 리터럴로 해석됨.
foo는 호출될 수 있으나 bar는 호출될 수 없음.
함수 리터럴에서 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자다 → 몸체 외부에서는 함수 이름으로 참조할 수 없음. 즉 함수를 가리키는 식별자가 없다.
- ➕ 함수내부에서는 함수 이름으로 재귀 작성 가능
var x=function aa(x){ if(x>0) aa(x-1) console.log("hello"); };
OK. 근데 식별자 foo 도 선언하고 할당한 적 없잖아? → JS 엔진이 암묵적으로 생성함.
JS 엔진은 함수 선언문 해석해서 함수 객체를 생성함. 근데 식별자 없으면 함수 객체를 호출할 수도 없고 무용지물이잖아.
⇒ JS 엔진은 암묵적으로 동일한 이름의 식별자를 생성하고, 거기에 함수 객체를 할당
function add(x,y){
return x+y;
} // 이 코드는 아래 코드처럼 작동한다는 의미
var add=function add(x,y){
return x+y;
}
함수 표현식
함수는 일급객체 이므로. 값처럼 사용할 수 있다. 그래서 함수 표현식으로 사용할 수 있는거.
즉, 함수 리터럴로 함수 만들고 값처럼 변수에 할당하는게 가능함.
// 함수 표현식
var add = function (x, y) {
return x + y;
};
console.log(add(2, 5)); // 7
함수 리터럴의 함수 이름 생략 가능함. 이러한 함수를 익명 함수
라고 한다.
함수 선언문은 표현식이 아닌 문이고 함수 표현식은 표현식인 문
함수 생성 시점과 팅
함수 선언문으로 작성한 함수는 호이스팅 되지만 함수 표현식으로 된건 안됨.
함수 호이스팅과 변수 호이스팅 미묘한 차이가 있다.
변수 호이스팅은 undefined 로 초기화 되지만 함수 호이스팅은 함수 객체로 초기화 된다.
즉, 변수는 선언만 호이스팅 되는 느낌이라면, 함수는 할당문까지 모두 호이스팅 되는 느낌.
console.log(v);
function v(){
return "hello"
};
/*
ƒ v(){
return "hello"
}
*/
그래서 함수 선언문 이전에도 함수 호출이 가능하다.
함수 표현식의 경우 함수 호이스팅이 아닌 변수 호이스팅이 일어난다. 그래서 함수 표현식의 함수 리터럴도 할당문이 실행되는 시점에 평가되어 함수 객체가 된다.
Function 생성자 함수
Function 생성자 함수에 매개변수 목록과 함수 몸체를 문자열로 전달하여 객체 생성
var add = new Function('x', 'y', 'return x + y');
console.log(add(2, 5)); // 7
- 클로저를 생성하지 않는 등 다른 함수들과 다르게 동작
var add1 = (function () {
var a = 10;
return function (x, y) {
return x + y + a;
};
}());
console.log(add1(1, 2)); // 13
var add2 = (function () {
var a = 10;
return new Function('x', 'y', 'return x + y + a;');
}());
console.log(add2(1, 2)); // ReferenceError: a is not defined
Funcion() 생성자로 생성한 함수는 항상 전역 스코프에 생성되기 때문에. 클로저로 사용될 수가 없다.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
https://stackoverflow.com/questions/37662435/functions-created-with-the-function-constructor-are-always-created-in-the-global
화살표 함수
화살표를 이용해서 간단하게 함수 선언
화살표 함수는 익명함수로 정의된다.
// 화살표 함수
const add = (x, y) => x + y;
console.log(add(2, 5)); // 7
생성자 함수를 사용할수 없으며, 기존 함수와 this 바인딩 방식이 다르고, prototype 프로퍼티가 없으며 arguments 객체를 생성하지 않는다.
26장 ES6 함수의 추가 기능#화살표 함수와 일반함수 차이 참고.
함수 호출
JS 에서 함수는 매개변수의 개수와 인수의 개수가 일치하는지 체크하지 않는다. 인수가 부족해서 할당되지 않은 매개변수는 undefined 값을 가진다.
모든 인수는 암묵적으로 arguments 객체의 프로퍼티로 보관된다.
function add(x, y) {
if (typeof x !== 'number' || typeof y !== 'number') {
// 매개변수를 통해 전달된 인수의 타입이 부적절한 경우 에러를 발생시킨다.
throw new TypeError('인수는 모두 숫자 값이어야 합니다.');
}
return x + y;
}
console.log(add(2)); // TypeError: 인수는 모두 숫자 값이어야 합니다.
console.log(add('a', 'b')); // TypeError: 인수는 모두 숫자 값이어야 합니다.
단축평가 이용해서 매개변수에 기본값 할당 할 수 있음.
function add(a, b, c) {
a = a || 0;
b = b || 0;
c = c || 0;
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
console.log(add(1, 2)); // 3
console.log(add(1)); // 1
console.log(add()); // 0
es6부터 매개변수 기본값 이 생김
인수 전달하지 않았을 경우와 undefined 리턴
function add(a = 0, b = 0, c = 0) {
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
console.log(add(1, 2)); // 3
console.log(add(1)); // 1
console.log(add()); // 0
다양한 함수의 형태
즉시 실행 함수
함수 정의와 동시에 즉시 호출되는 함수.
(function () {
}());
(function () {
})();
!function () {
}();
+function () {
}();
- 그룹 연산자로 감싸서 실행
- 단 한 번만 호출되며 다시 호출할 수 없다.
- 익명함수로 사용하는 것이 일반적
- 즉시 실행 함수 내에 코드를 작성하면 변수나 함수 이름의 충돌을 방지할 수 있다.
// 인수도 전달가능
(function (a,b) {
return a * b;
}(3,4));
재귀 함수
함수 내부에서 자신의 함수 이름을 사용해서 재귀 가능
콜백 함수
함수들이 비슷한 일을 공통적으로 수행하지만 내용이 다를 경우, 함수를 새롭게 정의해야함.
→ 함수의 변하지 않는 공통 로직을 정의해두고 함수를 가져와서 합성하면 됨
함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수
라고 하고, 매개변수를 통해 함수의 외부에서 콜백함수를 전달받은 함수를 고차 함수
라고 한다.
중첩 함수를 외부에서 내부로 주입하는 느낌.
콜백함수가 내부에서만 호출되면 익명 함수 리터럴로 정의하면서 곧바로 전달하는 것이 일반적
// 익명 함수 리터럴을 콜백 함수로 고차 함수에 전달한다.
// 익명 함수 리터럴은 repeat 함수를 호출할 때마다 평가되어 함수 객체를 생성한다.
repeat(5, function (i) {
if (i % 2) console.log(i);
}); // 1 3
콜백 함수는 비동기 처리에 활용되는 중요한 패턴
// 콜백 함수를 사용한 이벤트 처리
// myButton 버튼을 클릭하면 콜백 함수를 실행한다.
document.getElementById('myButton').addEventListener('click', function () {
console.log('button clicked!');
});
// 콜백 함수를 사용한 비동기 처리
// 1초 후에 메시지를 출력한다.
setTimeout(function () {
console.log('1초 경과');
}, 1000);
배열 고차 함수에서도 콜백 함수
많이 쓰임
// 콜백 함수를 사용하는 고차 함수 map
var res = [1, 2, 3].map(function (item) {
return item * 2;
});
console.log(res); // [2, 4, 6]
// 콜백 함수를 사용하는 고차 함수 filter
res = [1, 2, 3].filter(function (item) {
return item % 2;
});
console.log(res); // [1, 3]
// 콜백 함수를 사용하는 고차 함수 reduce
res = [1, 2, 3].reduce(function (acc, cur) {
return acc + cur;
}, 0);
console.log(res); // 6
순수 함수와 비순수 함수
어떤 외부 상태에 의존하지도 않고 변경하지도 않는 함수를 순수 함수 라고 하고, 외부 상태에 의존하고, 변경하는 함수를 비순수 함수 라고 한다.
순수 함수는 동일한 인수가 전달되면 항상 같은 값을 반환한다. 즉, 순수 함수는 함수 내부로 전달된 인수로만 반환값을 만든다.
또 다른 특징으로 함수의 외부 상태를 변경하지 않는다.
var count = 0; // 현재 카운트를 나타내는 상태
// 순수 함수 increase는 동일한 인수가 전달되면 언제나 동일한 값을 반환한다.
function increase(n) {
return ++n;
}
// 순수 함수가 반환한 결과값을 변수에 재할당해서 상태를 변경
count = increase(count);
console.log(count); // 1
count = increase(count);
console.log(count); // 2
비순수 함수는 외부 상태에 따라서 반환값이 달라지고, 외부 상태를 변경하는 부수효과가 있다.
객체를 인수로 전달받으면 비순수 함수 가 된다.
var count = 0; // 현재 카운트를 나타내는 상태: increase 함수에 의해 변화한다.
// 비순수 함수
function increase() {
return ++count; // 외부 상태에 의존하며 외부 상태를 변경한다.
}
// 비순수 함수는 외부 상태(count)를 변경하므로 상태 변화를 추적하기 어려워진다.
increase();
console.log(count); // 1
increase();
console.log(count); // 2